package com.googlecode.jwsm;

import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import java.util.*;
import java.util.jar.*;
import java.util.logging.*;
import java.util.regex.*;

import javax.servlet.http.*;

import org.jcommon.io.*;
import org.jcommon.util.*;
import org.jcommon.xml.*;
import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;
import org.jdom.transform.*;
import org.jdom.xpath.*;

import bsh.*;

/**
 * @author Matthew D. Hicks
 *
 */
public class Service {
	public static final FileFilter jarFilter = new FileFilter() {
		public boolean accept(File pathname) {
			if (pathname.getName().toLowerCase().endsWith(".jar")) {
				return true;
			}
			return false;
		}
	};

	private static HashSet ignoreMethods = null;

	private String name;
	private String mainClassName;
	private String instantiation;
	private Class mainClass;
	private Object object;
	private Calendar started;
	private long requested;
	private long errored;
	private Method[] methods;
	private String defaultMethod;
	private String[] visibleMethods;
	private boolean instanceInstantiation;
	private boolean debugMode;
	private boolean interceptWSDL;
	private HashMap outgoingXSLTMappings;
	private HashMap incomingXSLTMappings;

	private Interpreter interpreter;
	private JarClassLoader classLoader;

	public Service(String name, String mainClassName, String instantiation, String defaultMethod, String[] visibleMethods, boolean instanceInstantiation, boolean debugMode, boolean interceptWSDL) {
		this.name = name;
		this.mainClassName = mainClassName;
		this.instantiation = instantiation;

		this.defaultMethod = defaultMethod;
		this.visibleMethods = visibleMethods;
		if (this.visibleMethods == null) {
			this.visibleMethods = new String[0];
		}

		this.instanceInstantiation = instanceInstantiation;

		this.debugMode = debugMode;
		this.interceptWSDL = interceptWSDL;

		outgoingXSLTMappings = new HashMap();
		incomingXSLTMappings = new HashMap();

		requested = 0;
		errored = 0;
	}

	public String getOutgoingXSLTMapping(String methodName) {
		return (String)outgoingXSLTMappings.get(methodName);
	}

	public ClassLoader getClassLoader() {
		return classLoader;
	}

	public void setOutgoingXSLTMapping(String methodName, String xsltName) {
		outgoingXSLTMappings.put(methodName, xsltName);
	}

	public void setOutgoingXSLTMappings(HashMap xsltMappings) {
		this.outgoingXSLTMappings = xsltMappings;
	}

	public String getIncomingXSLTMapping(String methodName) {
		return (String)incomingXSLTMappings.get(methodName);
	}

	public void setIncomingXSLTMapping(String methodName, String xsltName) {
		incomingXSLTMappings.put(methodName, xsltName);
	}

	public void setIncomingXSLTMappings(HashMap xsltMappings) {
		this.incomingXSLTMappings = xsltMappings;
	}

	public void startLocal() throws ClassNotFoundException, EvalError, InstantiationException, IllegalAccessException {
		mainClass = getClass().getClassLoader().loadClass(mainClassName);
		if (hasCustomInstantiation()) {
			Interpreter i = new Interpreter();
			i.setClassLoader(classLoader);
			object = i.eval(instantiation);
		} else {
			object = mainClass.newInstance();
		}
		started = new GregorianCalendar();
	}

	public void start() throws InstantiationException, IllegalAccessException, MalformedURLException, ClassNotFoundException, EvalError {
		boolean customInstantiation = false;
		methods = null;
		if ((instantiation != null) && (instantiation.trim().length() > 0)) {
			customInstantiation = true;
		}
		HashSet buffer = new HashSet();
		File directory = new File(Uploader.directory, name);
		File[] jars = directory.listFiles(jarFilter);
		//URL[] urls = new URL[jars.length];
		for (int i = 0; i < jars.length; i++) {
			//urls[i] = jars[i].toURL();
			if (customInstantiation) addImports(buffer, jars[i]);
		}
		classLoader = new JarClassLoader(jars, Thread.currentThread().getContextClassLoader());
		mainClass = classLoader.loadClass(mainClassName);
		if (customInstantiation) {
			interpreter = new Interpreter();
			interpreter.setClassLoader(classLoader);
			StringBuffer s = new StringBuffer();
			Iterator iterator = buffer.iterator();
			while (iterator.hasNext()) {
				s.append("import " + iterator.next() + ".*;\r\n");
			}
			interpreter.eval(s.toString()); // Evaluate imports
			object = interpreter.eval(instantiation);
		} else {
			object = mainClass.newInstance();
		}
		started = new GregorianCalendar();
	}

	private void reinstantiate() throws EvalError, InstantiationException, IllegalAccessException {
		if ((instantiation != null) && (instantiation.trim().length() > 0)) {
			object = interpreter.eval(instantiation);
		} else {
			object = mainClass.newInstance();
		}
	}

	public void stop() {
		object = null;
		classLoader = null;
		started = null;
		requested = 0;
		errored = 0;
		methods = null;
	}

	public String undeploy() {
		stop();

		// Delete files and directory
		File directory = new File(Uploader.directory, name);
		File[] files = directory.listFiles();
		for (int i = 0; i < files.length; i++) {
			if (!files[i].delete()) {
				return "Unable to delete file " + files[i].getPath();
			}
		}
		if (!directory.delete()) {
			return "Unable to delete directory " + directory.getPath();
		}
		ServiceManager.getInstance().unregisterService(this);

		return null;
	}

	public String getStatus() {
		if (object != null) {
			return "Started";
		}
		return "Stopped";
	}

	public boolean isStarted() {
		if (object != null) return true;
		return false;
	}

	public String getName() {
		return name;
	}

	public Class getMainClass() {
		return mainClass;
	}

	public Calendar getStartDate() {
		return started;
	}

	public String getStarted() {
		if ((object == null) || (getStartDate() == null)) return "Not started";
		return StringUtilities.format(getStartDate(), "%MMMM% %d%, %yyyy% %hh%:%mm%:%ss%%amPM%");
	}

	public long getRequestCount() {
		return requested;
	}

	public long getErrorCount() {
		return errored;
	}

	public Method[] getMethods() {
		if (methods != null) {
			return methods;
		}
		ArrayList list;
		try {
			list = getValidMethods(mainClass);
		} catch (Throwable t) {
			Log.get().log(Level.SEVERE, "Exception occurred while getting valid methods", t);
			list = new ArrayList();
		}
		if (visibleMethods.length == 0) {
			// If no methods are specified, all are made visible
			methods = (Method[])list.toArray(new Method[list.size()]);
		} else {
			ArrayList m = new ArrayList();
			for (int i = 0; i < visibleMethods.length; i++) {
				for (int j = 0; j < list.size(); j++) {
					if (((Method)list.get(j)).getName().equals(visibleMethods[i])) {
						m.add(list.get(j));
					}
				}
			}
			methods = (Method[])m.toArray(new Method[m.size()]);
		}
		return this.methods;
	}

	public String getInstantiation() {
		return instantiation;
	}

	public boolean hasCustomInstantiation() {
		if ((instantiation != null) && (instantiation.trim().length() > 0)) {
			return true;
		}
		return false;
	}

	public boolean isDebug() {
		return debugMode;
	}

	private Method getMethod(String method) {
		if (method == null) method = defaultMethod;

		method = method.toLowerCase();
		Method[] methods = getMethods();
		// Compare exact matches
		for (int i = 0; i < methods.length; i++) {
			if ((methods[i].getName().toLowerCase().equals(method)) || (methods[i].getName().toLowerCase().equals("get" + method))) {
				return methods[i];
			}
		}
		// Do ends with comparison if a closer match can't be found
		for (int i = 0; i < methods.length; i++) {
			if (methods[i].getName().toLowerCase().endsWith(method)) {
				return methods[i];
			}
		}
		return null;
	}

	public String getDefaultMethod() {
		return defaultMethod;
	}

	public boolean isInstanceInstantiation() {
		return instanceInstantiation;
	}

	public boolean isInterceptWSDL() {
		return interceptWSDL;
	}

	public void request(String method, HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
		// Increment Requested
		requested++;

		Map queryMap = new HashMap();
		// Process GET query string values
		if (servletRequest.getQueryString() != null) {
			String[] parser = servletRequest.getQueryString().split("&");
			for (int i = 0; i < parser.length; i++) {
				String[] keyValue = parser[i].split("=");
				if (keyValue.length == 2) {
					queryMap.put(keyValue[0], keyValue[1]);
				}
			}
		}

		Log.fine("ServiceRequest: " + getName() + "." + method + " from " + servletRequest.getRemoteHost());

		PrintWriter writer = null;
		OutputStream os = null;
		try {
			Method m = getMethod(method);
			Object response = invoke(m, servletRequest, servletResponse);
			if (response == null) {
				// If the object is null we don't touch it or send anything back
				return;
			}
			if (response instanceof Throwable) {
				// If the method invocation threw an exception back we toss that up to a higher level
				throw (Throwable)response;
			} else if (response instanceof InputStream) {
				// If we got an InputStream back we simply stream it back
				servletResponse.setContentType("");
				InputStream is = (InputStream)response;
				os = servletResponse.getOutputStream();
				byte[] b = new byte[512];
				int len;
				while ((len = is.read(b)) > -1) {
					os.write(b, 0, len);
				}
				os.flush();
				is.close();
				os.close();
			} else if (servletRequest.getContentType().equals("application/octet-stream")) {
				// Received binary data, so we respond with binary data
				servletResponse.setContentType("application/octet-stream");
				os = servletResponse.getOutputStream();
				ObjectOutputStream oos = new ObjectOutputStream(os);
				oos.writeObject(response);
				oos.flush();
				oos.close();
			} else {
				// Send back a SOAP message
				writer = servletResponse.getWriter();
				servletResponse.setContentType("text/xml");
				// Set the XSLT
				File xsltFile = null;
				boolean applyDefault = true;
				if (queryMap.get("outgoingXSLT") != null) {
					XSLT xslt = ServiceManager.getInstance().getOutgoingXSLT((String)queryMap.get("outgoingXSLT"));
					xsltFile = new File(new File(Uploader.directory, ".xslts"), xslt.getFilename());
				}
				if ((xsltFile == null) && (getOutgoingXSLTMapping(method) != null)) {
					if (getOutgoingXSLTMapping(method).equals("<none>")) {
						applyDefault = false;
					} else {
						XSLT xslt = ServiceManager.getInstance().getOutgoingXSLT((String)getOutgoingXSLTMapping(method));
						if (xslt != null) {
							xsltFile = new File(new File(Uploader.directory, ".xslts"), xslt.getFilename());
						}
					}
				}
				if ((applyDefault) && (xsltFile == null)) {
					XSLT xslt = ServiceManager.getInstance().getOutgoingXSLT(response.getClass());
					if (xslt != null) {
						xsltFile = new File(new File(Uploader.directory, ".xslts"), xslt.getFilename());
					}
				}
				boolean useWSDL = false;
				if (Boolean.TRUE.equals(queryMap.get("UseWSDL"))) {
					useWSDL = true;
				}
				writeSOAP(this, response, xsltFile, writer, useWSDL);
			}
		} catch (Throwable t) {
			errored++;
			Log.get().log(Level.SEVERE, "Exception thrown while executing service " + getName() + "." + method + ".", t);
			try {
				if (os != null) {
					writer = new PrintWriter(os);
				} else if (writer == null) {
					writer = servletResponse.getWriter();
				}
				servletResponse.setContentType("text/xml");
				servletResponse.setStatus(350);
				writeSOAPFault(this, t, writer);
			} catch (Exception exc) {
				// TODO handle circumstance where writer is already established
				exc.printStackTrace();
			}
		} finally {
			Log.fine("Service exited.");
		}
	}

	private Object invoke(Method m, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IOException, JDOMException, SecurityException, ClassNotFoundException, InstantiationException, NoSuchMethodException, EvalError {
		if (instanceInstantiation) {
			reinstantiate();
		}
		
		HashMap<String, Object> queryMap = new HashMap<String, Object>();

		int parameterCount = 0;
		Object response = null;

		Class[] paramTypes = m.getParameterTypes();
		Object[] parameters = new Object[paramTypes.length];

		// Populate parameters for invocation
		int place;
		// Check for InputStream
		place = assignablePosition(paramTypes, parameters, InputStream.class);
		if (place > -1) {
			parameters[place] = servletRequest.getInputStream();
		}
		// Check for OutputStream
		place = assignablePosition(paramTypes, parameters, OutputStream.class);
		if (place > -1) {
			parameters[place] = servletResponse.getOutputStream();
		}
		// Check for HttpServletRequest
		place = assignablePosition(paramTypes, parameters, HttpServletRequest.class);
		if (place > -1) {
			parameters[place] = servletRequest;
		}
		// Check for HttpServletResponse
		place = assignablePosition(paramTypes, parameters, HttpServletResponse.class);
		if (place > -1) {
			parameters[place] = servletResponse;
		}

		if (isMissingParams(parameters)) {
			if ("application/octet-stream".equals(servletRequest.getContentType())) {
				// Binary input coming in
				ObjectInputStream ois = new ObjectInputStream(servletRequest.getInputStream()) {
					protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException {
						return Class.forName(desc.getName(), false, classLoader);
					}
				};
				try {
					for (int i = 0; i < parameters.length; i++) {
						if (parameters[i] == null) {
							// Populate object for parameter
							parameters[i] = ois.readObject();
						}
					}
				} catch(EOFException exc) {
					// Unable to find all the parameters in the object stream
					return new RuntimeException("Unable to populate all parameters from object stream.", exc);
				}
			} else {
				// We need to find content in the GET and/or POST data
				String postContent = StringUtilities.toString(servletRequest.getInputStream()).trim();
				if (postContent.length() == 0) {
					postContent = servletRequest.getQueryString();		// Use GET data instead
				}
				if (postContent.length() > 0) {
					// There is POST data, lets parse it
					if (isDebug()) {
						Log.get().log(DatabaseLogHandler.DEBUG, "Received: " + postContent);
					}
					if (postContent.startsWith("params")) {
						// Split out params into paramN values
						parameterCount = splitParams(postContent.substring("params=".length()), queryMap, ",");
					} else if (postContent.startsWith("param0")) {
						// Parse params into map
						String[] parser = postContent.trim().split("\\&");
						for (int i = 0; i < parser.length; i++) {
							String[] temp = parser[i].split("=");
							parameterCount++;
							if (temp.length > 1) {
								queryMap.put("param" + i, URLDecoder.decode(temp[1], "UTF-8"));
							}
						}
					} else if (postContent.indexOf(';') > -1) {
						parameterCount = splitParams(postContent, queryMap, ";");
					} else if (postContent.indexOf(',') > -1) {
						parameterCount = splitParams(postContent, queryMap, ",");
					} else {
						// Content is not parameterized, so we put it as param0
						queryMap.put("param0", postContent);
						parameterCount++;
					}
				}
				if (queryMap.containsKey("params")) {
					// Split out params into paramN values
					parameterCount = splitParams((String)queryMap.get("params"), queryMap, ",");
				}
	
				// Parse map params into parameters array
				SAXBuilder builder = new SAXBuilder();
				for (int i = 0; i < parameterCount; i++) {
					String content = (String)queryMap.get("param" + i);
					if (content == null) continue;
					if (content.trim().startsWith("<")) {
						Document document = builder.build(new StringInputStream(content));
						String prefix = document.getRootElement().getNamespacePrefix();
						Element body = (Element)XPath.selectSingleNode(document, "//" + prefix + ":Envelope/" + prefix + ":Body/JWSMRequest");
						if (body == null) {
							// Not a JWSMRequest, so lets check to see if it's SOAP
							body = (Element)XPath.selectSingleNode(document, "//" + prefix + ":Envelope/" + prefix + ":Body");
						} else {
							// It was an JWSMRequest, so we need to use the JWSMResponse on output
							queryMap.put("UseWSDL", Boolean.TRUE);
						}
						List nodes;
						if (body != null) {
							// There are children to iterate over
							nodes = body.getChildren();
							if (nodes.size() == 0) {
								content = body.getTextTrim();
								if (content.startsWith("<")) {
									// It's XML but wasn't parsed properly, so lets re-parse it
									if (content.startsWith("<?")) {
										content = content.substring(content.indexOf("?>") + 2);
									}
									try {
										document = builder.build(new StringInputStream("<root>" + content + "</root>"));
										nodes = document.getRootElement().getChildren();
									} catch (Exception exc) {
									}
								}
							}
							if (nodes.size() == 0) {
								// It's String data
								document = builder.build(new StringInputStream("<java.lang.String>" + content + "</java.lang.String>"));
								nodes = new ArrayList();
								nodes.add(document.getRootElement());
							}
						} else {
							// It's not SOAP, so we just get the root element
							nodes = new ArrayList();
							nodes.add(document.getRootElement());
						}
	
						// Cycle over nodes
						XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
						int position = 0;
						for (int j = 0; j < nodes.size(); j++) {
							if (nodes.get(j) instanceof Element) {
								Element e = (Element)nodes.get(i);
								position++;
								File xsltFile = null;
								if (queryMap.containsKey("incomingXSLT" + (position + 1))) {
									XSLT xslt = ServiceManager.getInstance().getIncomingXSLT((String)queryMap.get("incomingXSLT" + (position + 1)));
									xsltFile = new File(new File(Uploader.directory, ".xslts"), xslt.getFilename());
								} else if (queryMap.containsKey("incomingXSLT")) {
									XSLT xslt = ServiceManager.getInstance().getIncomingXSLT((String)queryMap.get("incomingXSLT"));
									xsltFile = new File(new File(Uploader.directory, ".xslts"), xslt.getFilename());
								}
								content = incomingXSLTConversion(m, position, outputter.outputString(e), xsltFile);
								if (isDebug()) {
									Log.get().log(DatabaseLogHandler.DEBUG, "Processed(" + i + ":" + j + "): " + content);
								}
								try {
									Object obj = XMLSerialization.fromXML(content, classLoader);
									int pos = assignablePosition(paramTypes, parameters, obj.getClass());
									if (pos > -1) {
										parameters[pos] = obj;
									}
								} catch (Exception exc) {
									int pos = assignablePosition(paramTypes, parameters, String.class);
									if (pos > -1) {
										parameters[pos] = content;
									}
								}
							}
						}
					} else {
						// It's not XML so we just use it as a String
	//					int pos = assignablePosition(paramTypes, parameters, String.class);
	//					if (pos > -1) {
	//						parameters[pos] = content;
	//					}
						// Try to match the params with the strings
						for (int j = 0; j < parameters.length; j++) {
							if (parameters[j] == null) {
								parameters[j] = StringUtilities.convert(content, paramTypes[j]);
								break;
							}
						}
					}
				}
			}
		}
		if (isDebug()) {
			for (int i = 0; i < parameters.length; i++) {
				Log.get().log(DatabaseLogHandler.DEBUG, "Parameter" + i + ": " + parameters[i]);
			}
		}
//		System.out.println("Invoking: " + m.getName() + ", " + m.getParameterTypes().length + ", " + parameters.length);
		response = m.invoke(object, parameters);

		return response;
	}

	private static final int splitParams(String params, Map map, String separator) throws UnsupportedEncodingException {
		String[] parser = URLDecoder.decode(params, "UTF-8").split(separator);
		for (int i = 0; i < parser.length; i++) {
			map.put("param" + i, parser[i]);
		}
		return parser.length;
	}

	private static final int assignablePosition(Class[] paramTypes, Object[] parameters, Class c) {
		for (int i = 0; i < paramTypes.length; i++) {
			if ((paramTypes[i].isAssignableFrom(c)) && (parameters[i] == null)) {
				return i;
			}
		}
		return -1;
	}

	private static final boolean isMissingParams(Object[] parameters) {
		for (int i = 0; i < parameters.length; i++) {
			if (parameters[i] == null) return true;
		}
		return false;
	}

	private String incomingXSLTConversion(Method method, int paramIndex, String content, File xsltFile) throws JDOMException, IOException {
		boolean applyDefault = true;
		if ((xsltFile == null) && (getIncomingXSLTMapping(method.getName()) != null)) {
			if (getIncomingXSLTMapping(method.getName()).equals("<none>")) {
				applyDefault = false;
			} else {
				XSLT xslt = ServiceManager.getInstance().getIncomingXSLT((String)getIncomingXSLTMapping(method.getName()));
				if (xslt != null) {
					xsltFile = new File(new File(Uploader.directory, ".xslts"), xslt.getFilename());
				}
			}
		}
		if ((applyDefault) && (xsltFile == null)) {
			XSLT xslt = null;
			if (method.getParameterTypes().length > paramIndex) {
				xslt = ServiceManager.getInstance().getIncomingXSLT(method.getParameterTypes()[paramIndex]);
			}
			if (xslt != null) {
				xsltFile = new File(new File(Uploader.directory, ".xslts"), xslt.getFilename());
			}
		}
		if (xsltFile != null) {
			// Do conversion
			SAXBuilder builder = new SAXBuilder();
			Document document = builder.build(new StringInputStream(content));
			XSLTransformer transformer = new XSLTransformer(xsltFile);
			document = transformer.transform(document);
			XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
			String returnContent = outputter.outputString(document);
			if (isDebug()) {
				Log.get().log(DatabaseLogHandler.DEBUG, "Applying XSLT Transformation to:" + content + ":");
				Log.get().log(DatabaseLogHandler.DEBUG, "XSLT Transformation result:" + returnContent + ":");
			}
			return returnContent;
		}
		return content;
	}

	private static final void addImports(HashSet buffer, File jarFile) {
		try {
			JarFile jar = new JarFile(jarFile);
			JarEntry entry;
			Enumeration e = jar.entries();
			while (e.hasMoreElements()) {
				entry = (JarEntry)e.nextElement();
				if ((!entry.isDirectory()) && (entry.getName().endsWith(".class"))) {
					buffer.add(entry.getName().substring(0, entry.getName().lastIndexOf('/')).replaceAll("/", "."));
				}
			}
		} catch (Exception exc) {
			exc.printStackTrace();
		}
	}

	public static final HashSet getIgnoreMethods() {
		if (ignoreMethods == null) {
			ignoreMethods = new HashSet(64);
			ignoreMethods.add("hashCode");
			ignoreMethods.add("wait");
			ignoreMethods.add("equals");
			ignoreMethods.add("notify");
			ignoreMethods.add("notifyAll");
			ignoreMethods.add("toString");
			ignoreMethods.add("getClass");
		}
		return ignoreMethods;
	}

	public static final ArrayList getValidMethods(Class c) {
		ArrayList list = new ArrayList();
		if (c != null) {
			Method[] methods = c.getMethods();
			for (int i = 0; i < methods.length; i++) {
				if (!getIgnoreMethods().contains(methods[i].getName())) {
					list.add(methods[i]);
				}
			}
		}
		return list;
	}

	public static final void writeSOAPFault(Service s, Object o, PrintWriter writer) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		String soap = getSOAPFault(o);
		if ((s != null) && (s.isDebug())) {
			Log.get().log(DatabaseLogHandler.DEBUG, "Response: " + soap);
		}
		writer.println(soap);
		writer.flush();
		writer.close();
	}

	public static final void writeSOAPFault(Service s, Object o, PrintStream writer) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		String soap = getSOAPFault(o);
		if ((s != null) && (s.isDebug())) {
			Log.get().log(DatabaseLogHandler.DEBUG, "Response: " + soap);
		}
		writer.println(soap);
		writer.flush();
		writer.close();
	}

	public static final void writeSOAP(Service s, Object o, File xslt, PrintWriter writer, boolean useWSDL) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, XSLTransformException {
		String soap = getSOAP(o, xslt, useWSDL);
		if ((s != null) && (s.isDebug())) {
			Log.get().log(DatabaseLogHandler.DEBUG, "Response: " + soap);
		}
		writer.println(soap);
		writer.flush();
		writer.close();
	}

	public static final void writeSOAP(Service s, Object o, File xslt, PrintStream writer, boolean useWSDL) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, XSLTransformException {
		String soap = getSOAP(o, xslt, useWSDL);
		if ((s != null) && (s.isDebug())) {
			Log.get().log(DatabaseLogHandler.DEBUG, "Response: " + soap);
		}
		writer.println(soap);
		writer.flush();
		writer.close();
	}

	public static final String getSOAPFault(Object o) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		Namespace ns = Namespace.getNamespace("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
		Element envelope = new Element("Envelope", ns);
		Document doc = new Document(envelope);

		Element header = new Element("Header", ns);
		envelope.addContent(header);

		Element body = new Element("Body", ns);
		Element fault = new Element("Fault", ns);
		body.addContent(fault);
		if (o instanceof String) {
			if (((String)o).matches(".*<.*:Envelope>.*")) {
				return (String)o; // It's a soap message, so we just return it
			} else {
				fault.setText(((String)o).replaceAll("<\\?.*\\?>", ""));
			}
		} else {
			XMLSerialization.appendXML(fault, o);
		}
		envelope.addContent(body);

		XMLOutputter outputter = new XMLOutputter();
		outputter.setFormat(Format.getPrettyFormat());
		return outputter.outputString(doc);
	}

	public static final String getSOAP(Object o, File xslt, boolean useWSDL) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, XSLTransformException {
		Namespace ns = Namespace.getNamespace("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
		Element envelope = new Element("Envelope", ns);
		Namespace ns2 = Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
		envelope.addNamespaceDeclaration(ns2);
		Namespace ns3 = Namespace.getNamespace("xsd", "http://www.w3.org/2001/XMLSchema");
		envelope.addNamespaceDeclaration(ns3);
		Document doc = new Document(envelope);

		//		Element header = new Element("Header", ns);
		//		envelope.addContent(header);

		Element body = new Element("Body", ns);
		envelope.addContent(body);
		if (useWSDL) {
			Element jwsmResponse = new Element("JWSMResponse", ns);
			jwsmResponse.setAttribute("type", "xsd:string", ns2);
			body.addContent(jwsmResponse);
			body = jwsmResponse;
		}
		if (o instanceof String) {
			if (((String)o).matches(".*<.*:Envelope>.*")) {
				return (String)o; // It's a soap message, so we just return it
			} else {
				body.setText(((String)o).replaceAll("<\\?.*\\?>", ""));
			}
		} else if (xslt != null) {
			// Apply the XSLT if we have one
			return getSOAP(XMLSerialization.toXMLString(o, xslt, false), null, useWSDL);
		} else {
			XMLSerialization.appendXML(body, o);
		}

		XMLOutputter outputter = new XMLOutputter();
		outputter.setFormat(Format.getPrettyFormat());
		return outputter.outputString(doc);
	}
}